#region legals - read before modifying or distributing
// Copyright iFinity.com.au 
// This is customised software developed entirely by iFinity. The below copyright messages should be followed 
// and any copies of this software should include this message.  
// Usage rights and restrictions:
// You may use this software without restriction on the number of installations in private and commercial applications
// You may make modifications to the source code of this software for your own requirements.
// You may not resell copies of this software or software derived from this source code as a licensed product.
// 
#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

using DotNetNuke.Common;
using DotNetNuke.Common.Utilities;
using DotNetNuke.Entities.Portals;
using DotNetNuke.Entities.Tabs;


namespace iFinity.DNN.Modules.FriendlyUrl

{
    internal static class TabDictController
    {
        private enum TabKeyPreference
        {
             TabOK
            ,TabRedirected
           , TabDeleted
        }

        /// <summary> 
        /// Builds a tab dictionary of all tabs in all portals, indexed by the alias and tab path 
        /// </summary> 
        /// <returns>A populated Dictionary(string,string) with the tabKey as the key, and the rewrite path as the value</returns> 
        /// <remarks>Also logs exceptions if duplicate tab paths are found for different tabs.</remarks> 
        internal static Dictionary<string, string> BuildTabDictionary(out PathSizes pathSizes, FriendlyUrlSettings settings)
        {
            Dictionary<string, string> tabDict = new Dictionary<string, string>();
            pathSizes = new PathSizes();
            pathSizes.MinAliasDepth = 10;
            pathSizes.MinTabPathDepth = 10;
            pathSizes.MaxAliasDepth = 0;
            pathSizes.MaxTabPathDepth = 0;
            //init the duplicate key check dictionary - disposed after the tab dictionary is built
            Dictionary<string, DupKeyCheck> dupCheck = new Dictionary<string, DupKeyCheck>();
            //get the list of tabs for all portals

            TabController tc = new TabController();
            ArrayList tabs = tc.GetAllTabs();

            int portalId = -1; PortalAliasController pac = new PortalAliasController();
            int homeTabId = -1;
            PortalController pc = new PortalController(); List<int> donePortals = new List<int>();
            //get the list of portals and index by ID
            ArrayList portals = pc.GetPortals();
            Dictionary<int, PortalInfo> allPortals = new Dictionary<int, PortalInfo>();

            foreach (PortalInfo portal in portals)
            {
                allPortals.Add(portal.PortalID, portal);

            }
            //Fix from Kristian Ranstrom to stop hitting GetPortalAliases repetitively, improves tab rebuild performance
            string PortalAliasCacheKey = "iFinity-PortalAliases";
            PortalAliasCollection allAliases;

            allAliases = (PortalAliasCollection)DataCache.GetCache(PortalAliasCacheKey);

            if (allAliases == null)
            {
                // get all aliases for this DNN install
                allAliases = pac.GetPortalAliases();
                // stuff this into the cache
                DataCache.SetCache(PortalAliasCacheKey, allAliases);
            }

            foreach (TabInfo tab in tabs)

            {
                //ASSUMPTION! The tabs are in rulePortalId, then tabId (inside the portal) order
                //the parmRedirects collection holds all the stuff needed to make up a Dictionary Entry
                int tabPathDepth = 0;
                PortalAliasCollection aliases = null;
                PortalInfo thisPortal; 
 
                if (tab.PortalID > -1 && allPortals.ContainsKey(tab.PortalID))
                {
                    if (portalId != tab.PortalID || aliases == null)
                    {
                        //this assumes each portal will be assessed in blocks
                        portalId = tab.PortalID;
                        thisPortal = allPortals[portalId];
                        //aliases = pac.GetPortalAliasByPortalID(portalId);//list of portal aliases
                        //replace above db call with cached copy
                        foreach (DictionaryEntry a in allAliases)
                        {
                            if (aliases == null)
                                aliases = new PortalAliasCollection();
                            if (allAliases[a.Key.ToString()].PortalID == portalId)
                                aliases.Add(a.Key.ToString(), (PortalAliasInfo)a.Value);
                        }

                        homeTabId = thisPortal.HomeTabId;//the home tabid of the portal
                        //homeTabId = pc.GetPortal(rulePortalId).HomeTabId; 
                        if (donePortals.Contains(portalId) == false) //only do this once per portal
                        {
                            foreach (DictionaryEntry entry in aliases) //and that is once per portal alias per portal
                            {
                                string httpAlias = aliases[entry.Key.ToString()].HTTPAlias;
                                AddStandardPagesToDict(tabDict, dupCheck, httpAlias, portalId, settings);

                                pathSizes.SetAliasDepth(httpAlias);
                            }
                            donePortals.Add(portalId);
                        }
                    }
                    
                    //check if this value has been excluded from being a friendly url
                    bool isExcluded = RewriteController.IsExcludedFromFriendlyUrls(tab, settings, true);
                    //process each entry for the alias
                    foreach (DictionaryEntry entry in aliases)
                    {
                        PortalAliasInfo portalAlias = (PortalAliasInfo)entry.Value;
                        string httpAlias = portalAlias.HTTPAlias;
                        if (!isExcluded)
                        {
                            //Add this tab to the dictionary 

                            tabPathDepth = AddTabToTabDict(tabDict, dupCheck, httpAlias, tab, settings, homeTabId);

                        }
                        else
                        {
                            //bug 589 : custom redirects added as 200 status not causing base urls to redirect
                            //string rewritePath = "[UseBase]";
                            

                            AddToTabDict(tabDict, dupCheck, httpAlias, tab.TabPath, "[UseBase]", tab.TabID, TabKeyPreference.TabRedirected, out tabPathDepth, true, false);

                        }
                        pathSizes.SetTabPathDepth(tabPathDepth);
                    }
                }
            }
            //do a check of the rebuildData object, to see if there is anything we needed to add to the dictionary
            PageIndexData rebuildData = (PageIndexData)DataCache.GetCache("rebuildData");
            if (rebuildData != null)
            {
                //there was rebuild data stored so we could do things post-dictionary rebuild
                if (rebuildData.LastPageKey != null && rebuildData.LastPageValue != null)
                {
                    if (tabDict.ContainsKey(rebuildData.LastPageKey) == false)
                    {
                        //add this item to the list of pages, even though it no longer exists
                        tabDict.Add(rebuildData.LastPageKey, rebuildData.LastPageValue);
                    }
                }
                //now clear out the rebuildData object, because we've checked and used it
                DataCache.RemoveCache("rebuildData");
            }
            
            return tabDict;
        }

        internal static void AddStandardPagesToDict(Dictionary<string,string> tabDict, Dictionary<string, DupKeyCheck> dupCheck, string httpAlias, int portalId, FriendlyUrlSettings settings)
        {
            int tabDepth = 0;
            
            //hard coded page paths 
            AddToTabDict(tabDict, dupCheck, httpAlias, "login", "?Portalid=" + portalId.ToString() + "&ctl=login", -1, TabKeyPreference.TabOK, out tabDepth, false, false);
            AddToTabDict(tabDict, dupCheck, httpAlias, "register", "?Portalid=" + portalId.ToString() + "&ctl=Register", -1, TabKeyPreference.TabOK, out tabDepth, false, false);
            AddToTabDict(tabDict, dupCheck, httpAlias, "terms", "?Portalid=" + portalId.ToString() + "&ctl=Terms", -1, TabKeyPreference.TabOK, out tabDepth, false, false);
            AddToTabDict(tabDict, dupCheck, httpAlias, "privacy", "?Portalid=" + portalId.ToString() + "&ctl=Privacy", -1, TabKeyPreference.TabOK, out tabDepth, false, false);
        }

        internal static int AddTabToTabDict(Dictionary<string, string> tabDict, Dictionary<string, DupKeyCheck> dupCheck, string httpAlias, TabInfo tab, FriendlyUrlSettings settings, int homeTabId)
 
        {

            //TODO: must refactor so that a deleted tab will be redirected, no matter which of the below three otpions it
            //fits.  Replace_space tabs that are deleted also need to be dealt with ,as do deleted tabs with redirects defined.
            string tabPath = GetTabPath(tab, settings.ReplaceSpaceWith);
            string rewritePath = ""; 
            int tabPathDepth = 0;

            //592 : check the permanent redirect value
            bool permanentRedirect = false;

            //determine the permanent redirect value using reflection to maintain backward compatibility
            //if you're paranoid and know your target environment you can hardcode the call to tab.PermanentRedirect;
            System.Reflection.PropertyInfo pi = typeof(TabInfo).GetProperty("PermanentRedirect");
            if (pi != null)
            {
                //get the permanent redirect value
                permanentRedirect = (bool)pi.GetValue(tab, null);
            }

            //determine the rewrite parameter
            //for deleted, expired or pages not enabled yet, direct to the home page if the setting is enabled
            //534 : tab is disabled, mark as deleted (don't want to cause duplicate tab warnings)
            bool isDeleted = (tab.IsDeleted || tab.DisableLink || (tab.EndDate < DateTime.Now & tab.EndDate > DateTime.MinValue && tab.StartDate > DateTime.Now && tab.StartDate > DateTime.MinValue));
            if (isDeleted && settings.DeletedTabHandling == DeletedTabHandlingTypes.Do301RedirectToPortalHome)
            {
                rewritePath = RedirectTokens.CreateRewritePathAndRedirectReason(homeTabId, UrlAction.RedirectReason.Page_Deleted);
                isDeleted = true;
            }
            else if (!isDeleted )
            {
                //for all other pages, rewrite to the correct tabId for that page
                //592 : new permanentRedirect value
                if (permanentRedirect)
                    rewritePath = RedirectTokens.CreateRewritePathAndRedirectReason(tab.TabID, UrlAction.RedirectReason.Tab_Permanent_Redirect);
                else
                    rewritePath = RedirectTokens.CreateRewritePathAndRedirectReason(tab.TabID, UrlAction.RedirectReason.Not_Redirected);

            }

            if (tabPath != tab.TabPath)
            {
                //when the generated tab path is different to the standard tab path, character substituion has happened
                //this entry is going to have space substitution in it, so it is added into the dictionary with a delete notification and a 301 replaced 
                //this entry is the 'original' (spaces removed) version ie mypage
                string substituteRewritePath = rewritePath;
                if (tabPath.Contains(settings.ReplaceSpaceWith))  //replaced back to the spaces-replaced version (ie mypage -> my-page)
                    substituteRewritePath = RedirectTokens.AddRedirectReasonToRewritePath(substituteRewritePath, UrlAction.RedirectReason.Spaces_Replaced);
                else  //not a spaces replacement, must be a custom redirect
                    substituteRewritePath = RedirectTokens.AddRedirectReasonToRewritePath(substituteRewritePath, UrlAction.RedirectReason.Custom_Redirect);

                TabKeyPreference preference = TabKeyPreference.TabRedirected;
                if (isDeleted)
                    // if the tab is actually deleted, downgrade the preference to 'deleted'.  Any other tabs with the same path that
                    // are redirected but not deleted should take preference
                    preference = TabKeyPreference.TabDeleted;
                AddToTabDict(tabDict, dupCheck, httpAlias, tab.TabPath, substituteRewritePath, tab.TabID, preference,out tabPathDepth, settings.CheckForDupUrls, isDeleted);
            }

            //check for permanent redirects as specified in the core dnn permanent redirect property
            if (permanentRedirect)
                AddPermanentRedirectToDictionary(tabDict, dupCheck, httpAlias, tab, tabPath, ref rewritePath, out tabPathDepth, settings.CheckForDupUrls, isDeleted, settings.RedirectUnfriendly);

            //594 : disabled / external url pages cannot navigate to settings page
            if (tab.DisableLink || (tab.Url != null && tab.Url != ""))
            {
                string settingsUrl = tabPath.Replace("//", "/") + "/ctl/Tab";
                string settingsRewritePath = "?TabId=" + tab.TabID.ToString() + "&ctl=Tab"; //no redirect on the ctl/Tab url
                AddToTabDict(tabDict, dupCheck, httpAlias, settingsUrl, settingsRewritePath, tab.TabID, TabKeyPreference.TabRedirected, out tabPathDepth, settings.CheckForDupUrls, isDeleted);
            }

            //every tab is added to the dictionary, unless they are deleted and not wanted (ie setting says let it 404)
            if (!isDeleted || (isDeleted && settings.DeletedTabHandling == DeletedTabHandlingTypes.Do301RedirectToPortalHome))
            {

                //add the 'standard' Url in
                if (tab.TabID == homeTabId && settings.RedirectUnfriendly == true)
                {
                    //home page shoudl be redirected back to the site root
                    /*if (rewritePath.Contains("&do301=true") == false)
                        rewritePath += "&do301=true&rr=hp";*/
                    rewritePath = RedirectTokens.AddRedirectReasonToRewritePath(rewritePath, UrlAction.RedirectReason.Site_Root_Home);
                    AddToTabDict(tabDict, dupCheck, httpAlias, tabPath, rewritePath, tab.TabID, TabKeyPreference.TabOK, out tabPathDepth, settings.CheckForDupUrls, isDeleted);
                }
                else
                    AddToTabDict(tabDict, dupCheck, httpAlias, tabPath, rewritePath, tab.TabID, TabKeyPreference.TabOK, out tabPathDepth, settings.CheckForDupUrls, isDeleted);
            }
            return tabPathDepth;
        }

        /// <summary>
        /// Adds in any redirects as specified by the core DNN property 'permanentRedirect'
        /// </summary>
        internal static void AddPermanentRedirectToDictionary(Dictionary<string, string> tabDict, Dictionary<string, DupKeyCheck> dupCheck, string httpAlias, TabInfo tab, string tabPath, ref string rewritePath, out int tabPathDepth, bool checkForDupUrls, bool isDeleted, bool redirectUnfriendly)
        {
            //because we have to catch all versions of this in the dictionary, then we have to add the 'base' url
            string redirectUrl = tab.TabPath + "/tabid/" + tab.TabID.ToString() + "/default";
            AddToTabDict(tabDict, dupCheck, httpAlias, redirectUrl, rewritePath, tab.TabID, TabKeyPreference.TabRedirected, out tabPathDepth, checkForDupUrls, isDeleted);
            //and put in the name-less one as well, just in case a prior version of the site was runnign without the tabnames (urlformat=sefriendly)
            redirectUrl = "/tabid/" + tab.TabID.ToString() + "/default";
            AddToTabDict(tabDict, dupCheck, httpAlias, redirectUrl, rewritePath, tab.TabID, TabKeyPreference.TabRedirected, out tabPathDepth, checkForDupUrls, isDeleted);
            //finally, put one in for the ctl/tab combination, so that you can actually get to the page settings
            redirectUrl = tabPath.Replace("//","/") + "/ctl/Tab";
            string settingsRewritePath = "?TabId=" + tab.TabID.ToString() + "&ctl=Tab"; //no redirect on the ctl/Tab url
            AddToTabDict(tabDict, dupCheck, httpAlias, redirectUrl, settingsRewritePath, tab.TabID, TabKeyPreference.TabRedirected, out tabPathDepth, checkForDupUrls, isDeleted);
    
        }

        internal static string GetTabPath(TabInfo tab, string replaceSpacesWith)
        {
            string tabPath = "";
            if (replaceSpacesWith.Length > 0)
            {
                //find out what the tab name is with no spaces in it 
                tabPath = TabPathHelper.BuildTabPathWithReplacement(tab, " ", replaceSpacesWith);
            }
            else
            {
                tabPath = tab.TabPath;
            }
            return tabPath;
        }

        internal static void StashTabDictionary(Dictionary<string, string> tabDictionary, PathSizes depthInfo, string reason, FriendlyUrlSettings settings)
        {
            CacheController cc = new CacheController();
            cc.SetCache(tabDictionary, depthInfo, settings);
            if (settings.LogCacheMessages)
            {
                DotNetNuke.Services.Log.EventLog.EventLogController elc = new DotNetNuke.Services.Log.EventLog.EventLogController();
                DotNetNuke.Services.Log.EventLog.LogInfo logValue = new DotNetNuke.Services.Log.EventLog.LogInfo();
                logValue.LogTypeKey = "HOST_ALERT";

                logValue.AddProperty("iFinity.FriendlyUrlProvider.Message", "Tab Dictionary built and cached: " + tabDictionary.Values.Count.ToString() + " items.  Reason: " + reason);

                elc.AddLog(logValue);
            }
        }

        public static void InvalidateDictionary(string reason, PageIndexData rebuildData, int portalId)
        {
            DataCache.RemoveCache("tabDict");
            DataCache.RemoveCache("depthInfo");
            //if supplied, store the rebuildData for when the dictionary gets rebuilt
            //this is a way of storing data between dictionary rebuilds
            if (rebuildData != null)
                DataCache.SetCache("rebuildData", rebuildData);



            DotNetNuke.Services.Log.EventLog.EventLogController elc = new DotNetNuke.Services.Log.EventLog.EventLogController();
            DotNetNuke.Services.Log.EventLog.LogInfo logValue = new DotNetNuke.Services.Log.EventLog.LogInfo();
            logValue.LogTypeKey = "HOST_ALERT";

            logValue.AddProperty("iFinity.FriendlyUrlProvider.Message", "Tab Dictionary Removed.  Reason: " + reason);

            elc.AddLog(logValue);

        }
        /// <summary>
        /// Adds another entry to the existing dictionary
        /// </summary>
        /// <param name="key">Key to add</param>
        /// <param name="value">Value to add</param>
        /// <returns>true if it was added, false if it wasn't</returns>
        internal static bool AddEntryToDictionary(string key, string value, FriendlyUrlSettings settings)
        {
            bool succeeded = false;
            object cachedObj = DataCache.GetCache("tabDict");
            if (cachedObj != null)
            {
                Dictionary <string, string> dict = ((Dictionary<string, string>)cachedObj);
                if (key != null && value != null)
                {
                    if (dict.ContainsKey(key) == false)
                    {
                        cachedObj = DataCache.GetCache("depthInfo");
                        if (cachedObj != null)
                        {
                            dict.Add(key, value);
                            //restash the dictionary
                            StashTabDictionary(dict, (PathSizes)cachedObj, "New Url created, replaces " + value, settings);
                            succeeded = true;
                        }
                    }
                }
            }
            return succeeded;
        }
        /// <summary> 
        /// Gets the Tab Dictionary from the DataCache memory location, if it's empty or missing, builds a new one 
        /// </summary> 
        /// <param name="minTabPathDepth">ByRef parameter to return the minimum tab path depth (the number of '/' in the tab path)</param> 
        /// <param name="maxTabPathDepth">ByRef parameter to return the maximum tab path depth (the number of '/' in the tab path)</param> 
        /// <param name="minAliasPathDepth">ByRef parameter to return the minimum alias path depth (the number of '/' in the alias path</param> 
        /// <param name="maxAliasPathDepth">ByRef parameter to return the maximum alias path depth (the number of '/' in the alias path)</param> 
        /// <returns>Dictionary (string, string) of Tab paths in tab key, with the rewrite path as the value</returns> 
        /// <remarks></remarks> 
        internal static Dictionary<string, string> FetchTabDictionary(out int minTabPathDepth, out int maxTabPathDepth, out int minAliasPathDepth, out int maxAliasPathDepth, FriendlyUrlSettings settings, bool forceRebuild, bool bypassCache)
        {
            PathSizes depthInfo = null;
            Dictionary<string, string> dict = null;
            //check for the tab dictionary in the DataCache 
            object tabDict = DataCache.GetCache("tabDict");
            string reason = "";
            reason = "Force Rebuild " + forceRebuild.ToString() + ";";
            reason += "Bypass Cache " + bypassCache.ToString() + ";";
            //System.Diagnostics.Trace.Assert(tabDict == null, "GetCache('tabDict') returned null (before get)");
            if (dict == null)
                reason += "Dict=null;";
            else
                reason += "Dict= not null;";
            if (tabDict != null && forceRebuild == false && bypassCache == false)
            {
                dict = ((Dictionary<string, string>)tabDict);
                tabDict = DataCache.GetCache("depthInfo");
                if (((tabDict != null)))
                {
                    depthInfo = (PathSizes)tabDict;
                    minTabPathDepth = depthInfo.MinTabPathDepth;
                    maxTabPathDepth = depthInfo.MaxTabPathDepth;
                    minAliasPathDepth = depthInfo.MinAliasDepth;
                    maxAliasPathDepth = depthInfo.MaxAliasDepth;
                }
                else
                {
                    //this is really just a fallback, in case there is no depth info. In reality it should never be called 
                    minTabPathDepth = 0;
                    minAliasPathDepth = 0;
                    maxTabPathDepth = 20;
                    maxAliasPathDepth = 20;
                }
            }
            else
            {
                //the cached dictionary was null or forceReubild = true or bypassCache = true, so go get a new dictionary
                dict = TabDictController.BuildTabDictionary(out depthInfo, settings);
                if (bypassCache == false)  //only cache if bypass not switched on
                    TabDictController.StashTabDictionary(dict , depthInfo, reason, settings);
                
                minTabPathDepth = depthInfo.MinTabPathDepth;
                maxTabPathDepth = depthInfo.MaxTabPathDepth;
                minAliasPathDepth = depthInfo.MinAliasDepth;
                maxAliasPathDepth = depthInfo.MaxAliasDepth;
            }
            return dict;
       } 
        /// <summary> 
        /// Adds a tab entry into the tab dictionary 
        /// </summary> 
        /// <param name="tabDict">The tabDictionary </param> 
        /// <param name="dupCheckDict">The dupCheck dictionary, which is used to test for duplicate url paths between different portals</param> 
        /// <param name="httpAlias">The current portal Alias</param> 
        /// <param name="tabPath">The path of the tab, as specified from the TabInfo object</param> 
        /// <param name="destParms">The rewrite parameters to use when this tab is requested</param> 
        /// <param name="keyDupAction">true if the tab is deleted.</param> 
        /// <param name="tabPathDepth">ByRef parameter to return the depth of the tab path (the number of '/' in the path)</param> 
        /// <remarks>Performs duplication checking as well.</remarks> 
        /// <history>
        /// bc  20/02/09 Add in redirect changes for permanentRedirect option in 4.9.2/5.0.1 and above
        /// </history>
        private static void AddToTabDict(Dictionary<string, string> tabDict, Dictionary<string, DupKeyCheck> dupCheckDict, string httpAlias, string tabPath, string rewrittenPath, int tabId, TabKeyPreference keyDupAction,out int tabPathDepth, bool checkForDupUrls, bool isDeleted)
        {
            //remove leading '/' and convert to lower for all keys 
            string tabPathSimple = tabPath.Replace("//", "/").ToLower();
            tabPathDepth = tabPathSimple.Length - tabPathSimple.Replace("/", "").Length;
            if ((tabPathSimple.Length > 0 && tabPathSimple[0] == '/'))
                tabPathSimple = tabPathSimple.Substring(1);

            //Contruct the tab key for the dictionary. Using :: allows for separation of portal alias and tab path. 
            string tabKey = (httpAlias + "::" + tabPathSimple).ToLower();

            //construct the duplicate key check
            string dupKey = (httpAlias + "/" + tabPathSimple).ToLower();
            if (!(dupKey[dupKey.Length - 1] == '/'))
                dupKey += "/";

            //now make sure there is NEVER a duplicate key exception by testing for existence first            
            if (tabDict.ContainsKey(tabKey))
            {
                //it's possible for a tab to be deleted and the tab path repeated. 
                //the dictionary must be checked to ascertain whether the existing tab 
                //should be replaced or not.  If the action is 'TabOK' it means
                //replace the entry regardless.  If the action is 'TabRedirected' it means
                //replace the existing dictionary ONLY if the existing dictionary entry is a 
                //deleted tab.
                bool replaceTab = (keyDupAction == TabKeyPreference.TabOK);  //default, replace the tab
                if (replaceTab == false)
                {
                    //ok, the tab to be added is either a redirected or deleted tab
                    //get the existing entry
                    DupKeyCheck foundTab = dupCheckDict[dupKey];
                    //a redirected tab will replace a deleted tab
                    if (foundTab.IsDeleted && keyDupAction == TabKeyPreference.TabRedirected)
                        replaceTab = true;
                    if (foundTab.TabIdOriginal == "-1")
                        replaceTab = true;
                }
                if (replaceTab && !isDeleted) //don't replace if the incoming tab is deleted
                {
                    //remove the previous one 
                    tabDict.Remove(tabKey);
                    //add the new one 
                    tabDict.Add(tabKey, Globals.glbDefaultPage + rewrittenPath);
                }
            }
            else
            {
                //just add the tabkey into the dictionary
                tabDict.Add(tabKey, Globals.glbDefaultPage + rewrittenPath);
            }

            //checking for duplicates means throwing an exception when one is found, but this is just logged to the event log
            if (dupCheckDict.ContainsKey(dupKey))
            {
                DupKeyCheck foundTAb = dupCheckDict[dupKey];
                if ((foundTAb.IsDeleted == false && isDeleted == false) //found is not deleted, this tab is not deleted
                    && keyDupAction == TabKeyPreference.TabOK && foundTAb.TabIdOriginal != "-1") //-1 tabs are login, register, privacy etc
                {
                    //check whether to log for this or not
                    if (checkForDupUrls && foundTAb.TabIdOriginal != tabId.ToString()) //dont' show message for where same tab is being added twice)
                    {
                        //there is a naming conflict where this alias/tab path could be mistaken 
                        int tabIdOriginal = -1; string tab1Name = "", tab2Name = "";
                        if (int.TryParse(foundTAb.TabIdOriginal, out tabIdOriginal))
                        {
                            TabController tc = new TabController();
                            TabInfo tab1 = tc.GetTab(tabIdOriginal);
                            TabInfo tab2 = tc.GetTab(tabId);
                            if (tab1 != null) tab1Name = tab1.TabName + " [" + tab1.TabPath + "]";
                            if (tab2 != null) tab2Name = tab2.TabName + " [" + tab2.TabPath + "]";
                        }

                        string msg = "Page naming conflict. Url of (" + foundTAb.TabPath + ") resolves to two separate pages (" + tab1Name + " [tabid = " + foundTAb.TabIdOriginal + "], " + tab2Name + " [tabid = " + tabId.ToString() + "]). Only the second page will be shown for the url.";
                        string msg2 = "PLEASE NOTE : this is an information message only, this message does not affect site operations in any way.";

                        //771 : change to admin alert instead of exception
                        DotNetNuke.Services.Log.EventLog.EventLogController elc = new DotNetNuke.Services.Log.EventLog.EventLogController();
                        //log a host alert
                        DotNetNuke.Services.Log.EventLog.LogInfo logValue = new DotNetNuke.Services.Log.EventLog.LogInfo();
                        logValue.LogTypeKey = "HOST_ALERT";
                        logValue.AddProperty("Friendly Url Provider Duplicate Url Warning", "Page Naming Conflict");
                        logValue.AddProperty("Duplicate Page Details", msg);
                        logValue.AddProperty("Warning Information", msg2);
                        logValue.AddProperty("Suggested Action", "Rename one or both of the pages to ensure a unique Url");
                        logValue.AddProperty("Hide this message", "To stop this message from appearing in the log, uncheck the option for 'Produce an Exception in the Site Log if two pages have the same name/path?' in the Friendly Url Provider configuration by setting checkForDupUrls=false in the web.config.");
                        logValue.AddProperty("Thread Id", System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
                        elc.AddLog(logValue);
                    }
                }
                else
                {
                    dupCheckDict.Remove(dupKey);
                    //add this tab to the duplicate key dictionary 
                    dupCheckDict.Add(dupKey, new DupKeyCheck(dupKey, tabId.ToString(), dupKey, isDeleted));
                }
            }
            else
            {
                //add this tab to the duplicate key dictionary - the dup key check dict is always maintained 
                //regardless of whether checking is employed or not
                dupCheckDict.Add(dupKey, new DupKeyCheck(dupKey, tabId.ToString(), dupKey, isDeleted));
            }
            
        }
    }

    internal static class RedirectTokens
    {
        #region Redirect Reason Token Values
        /// <summary>
        /// SetRedirectReason extracts the redirect value from the rewrite url and 
        /// sets the value in the retVal variable.  This is used when rewriting the url
        /// </summary>
        /// <param name="rewrittenUrl">Rewritten url as found in page dictionary</param>
        /// <param name="result">The current rewrite result.</param>
        /// <param name="wasParms">true if there are parameters in the path, false if not</param>
        /// <param name="settings">current FriendlyUrlSettings object</param>
        /// <param name="action">New action value for UrlAction object</param>
        /// <param name="reason">New redirect reason value for UrlAction object</param>
        /// <param name="newUrl">Url to used for rewrite process</param>
        /// <returns>the new Url, with any replacements done.  Replacements occur when a reason token
        /// was stored in the tab dictionary entry just to indicate a redirect reason.</returns>
        internal static void DetermineRedirectReasonAndAction(string rewrittenUrl, UrlAction result, bool wasParms, FriendlyUrlSettings settings, out string newUrl, out UrlAction.RedirectReason reason, out UrlAction.ActionType action)
        {
            newUrl = rewrittenUrl;
            action = result.Action;
            reason = result.Reason;
            if (newUrl.Contains("do301=true"))
            {
                bool clear301Token = false;
                //get the list of redirect reason tokens
                List<string> redirectReasons = GetRedirectTokensFromRewritePath(rewrittenUrl);
                foreach (string rrTkn in redirectReasons)
                {
                    switch (rrTkn)
                    {
                        case "up":
                            if (wasParms)
                            {
                                newUrl = newUrl.Replace("&rr=upa", "");
                                if (reason == UrlAction.RedirectReason.Not_Redirected)
                                {
                                    reason = UrlAction.RedirectReason.User_Profile_Url;
                                    action = UrlAction.ActionType.CheckFor301;
                                }
                            }
                            else
                            {
                                //if no parms, then we're not doing a userprofileaction redirect
                                reason = UrlAction.RedirectReason.Custom_Redirect;
                                //then check for a 301 redirect
                                action = UrlAction.ActionType.CheckFor301;
                                clear301Token = true;
                            }

                            break;

                        case "dl":
                            if (settings.DeletedTabHandling == DeletedTabHandlingTypes.Do404Error)
                            {  //626 Deleted tab hanlding not working properyly
                                action = UrlAction.ActionType.Output404; //output a 404 as per settings
                            }
                            reason = UrlAction.RedirectReason.Page_Deleted;
                            clear301Token = false;
                            break;

                        case "pr":
                            reason = UrlAction.RedirectReason.Tab_Permanent_Redirect;
                            action = UrlAction.ActionType.Redirect301;
                            clear301Token = false;
                            break;

                        case "sr":
                            reason = UrlAction.RedirectReason.Spaces_Replaced;
                            action = UrlAction.ActionType.Redirect301;
                            clear301Token = true; //in case of other 301 redirect request
                            break;

                        case "hp":
                            if (wasParms == true)
                            {
                                //cancel the home page replaced if there were parameters added and page extensions
                                //are in use - otherwise a 404 will occur for the relative path
                                reason = UrlAction.RedirectReason.Not_Redirected;
                                action = UrlAction.ActionType.Continue;
                                clear301Token = true;
                            }
                            else
                            {
                                reason = UrlAction.RedirectReason.Site_Root_Home;
                                action = UrlAction.ActionType.Redirect301;
                                clear301Token = false;
                            }
                            break;


                        default:
                            if (reason == UrlAction.RedirectReason.Not_Redirected)
                            {
                                reason = UrlAction.RedirectReason.Custom_Redirect;
                                clear301Token = false;
                            }
                            break;
                    }
                }
                newUrl = RemoveAnyRedirectTokens(newUrl, clear301Token);
            }
        }
        internal static void DetermineRedirectReasonAndAction(bool old, string rewrittenUrl, UrlAction result, bool wasParms, FriendlyUrlSettings settings, out string newUrl, out UrlAction.RedirectReason reason, out UrlAction.ActionType action)
        {
            newUrl = rewrittenUrl;
            action = result.Action;
            reason = result.Reason;
            //set the 301 replaced reason if there was a replaced
            if (newUrl.Contains("do301=true"))
            {
                if (newUrl.Contains("&rr=del"))
                {
                    if (settings.DeletedTabHandling == DeletedTabHandlingTypes.Do404Error)
                    {  //626 Deleted tab hanlding not working properyly
                        action = UrlAction.ActionType.Output404; //output a 404 as per settings
                        newUrl = newUrl.Replace("&rr=del", "");//get rid of the 301 redirect tag
                    }
                    newUrl = newUrl.Replace("&rr=del", "");
                    reason = UrlAction.RedirectReason.Page_Deleted;
                }
                else
                    //592 new permanentRedirect value
                    if (newUrl.Contains("&rr=pr"))
                    {
                        newUrl = newUrl.Replace("&rr=pr", "");
                        reason = UrlAction.RedirectReason.Tab_Permanent_Redirect;
                        action = UrlAction.ActionType.Redirect301;
                    }
                    else
                    {
                        if (newUrl.Contains("&rr=sr"))
                        {
                            newUrl = newUrl.Replace("&rr=sr", "");
                            newUrl = newUrl.Replace("&do301=true", "");//in case there is a do301 request on there as well
                            reason = UrlAction.RedirectReason.Spaces_Replaced;
                            action = UrlAction.ActionType.Redirect301;
                        }
                        else
                            if (newUrl.Contains("&rr=hp"))
                            {
                                newUrl = newUrl.Replace("&rr=hp", "");
                                if (wasParms == true)
                                {
                                    //cancel the home page replaced if there were parameters added and page extensions
                                    //are in use - otherwise a 404 will occur for the relative path
                                    reason = UrlAction.RedirectReason.Not_Redirected;
                                    action = UrlAction.ActionType.Continue;
                                    newUrl = newUrl.Replace("&do301=true", "");
                                    newUrl = newUrl.Replace("?do301=true", "");
                                }
                                else
                                    reason = UrlAction.RedirectReason.Site_Root_Home;

                            }
                            else
                            {

                                    if (reason == UrlAction.RedirectReason.Not_Redirected)
                                    {
                                        reason = UrlAction.RedirectReason.Custom_Redirect;
                                    }
                            }
                    }
                //finally, clear the rewrittenUrl of any trace of the tokens
                newUrl = RemoveAnyRedirectTokens(newUrl, false);
            }
        }
        /// <summary>
        /// Creates a rewrite path and adds a redirect reason if required
        /// </summary>
        /// <param name="tabId"></param>
        /// <param name="reason"></param>
        /// <returns></returns>
        internal static string CreateRewritePathAndRedirectReason(int tabId, UrlAction.RedirectReason reason)
        {
            string rewritePath = "?TabId=" + tabId.ToString();
            string reasonRewriteToken = GetRedirectReasonRewriteToken(reason);
            if (reason != UrlAction.RedirectReason.Not_Redirected)
            {
                rewritePath += "&do301=true" + reasonRewriteToken;
            }
            return rewritePath;
        }
        /// <summary>
        /// Adds on a redirect reason to the rewrite path
        /// </summary>
        /// <param name="existingRewritePath"></param>
        /// <param name="reason"></param>
        /// <returns></returns>
        internal static string AddRedirectReasonToRewritePath(string existingRewritePath, UrlAction.RedirectReason reason)
        {
            bool already301 = false;
            string result = existingRewritePath;
            if (existingRewritePath.Contains("do301=true"))
                already301 = true;
            //the reasonToken helps the rewrite process determine why a redirect is required
            //after the token is stored in the page dictionary
            string reasonToken = GetRedirectReasonRewriteToken(reason);
            //the do301 token is added to the rewrite path to indicate a 301 is desired.
            if (!already301)
                result += "&do301=true" + reasonToken;
            else 
                result += reasonToken;
            //special case : add the number of times a 301 has been requested
            if (already301 && reason == UrlAction.RedirectReason.User_Profile_Url)
                result += "&num301=2";
            return result;
        }
        private static List<string> GetRedirectTokensFromRewritePath(string rewritePath)
        {
            List<string> reasons = new List<string>();
            MatchCollection matches = Regex.Matches(rewritePath, @"&rr=(?<rr>[^&].)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
            foreach (Match match in matches)
            {
                if (match.Success)
                {
                    Group rrG = match.Groups["rr"];
                    if (rrG.Success)
                    {
                        string rr = match.Groups["rr"].Value;
                        reasons.Add(rr);
                    }
                }
            }
            return reasons;
        }
        /// <summary>
        /// Removes all traces of redirect tokens from a rewrite path
        /// </summary>
        /// <param name="rewritePath"></param>
        /// <returns></returns>
        private static string RemoveAnyRedirectTokens(string rewritePath, bool clear301Token)
        {
            string result = rewritePath;
            result = Regex.Replace(rewritePath, @"&rr=(?<rr>[^&].)", "");
            if (clear301Token)
                result = result.Replace("&do301=true", "");
            return result;
        }
        private static string GetRedirectReasonRewriteToken(UrlAction.RedirectReason reason)
        {
            string result = "";
            switch (reason)
            {
                case UrlAction.RedirectReason.Page_Deleted:
                    result = "&rr=dl";
                    break;
                case UrlAction.RedirectReason.Tab_Permanent_Redirect:
                    result = "&rr=pr";
                    break;
                case UrlAction.RedirectReason.Spaces_Replaced:
                    result = "&rr=sr";
                    break;
                case UrlAction.RedirectReason.Site_Root_Home:
                    result = "&rr=hp";
                    break;
                case UrlAction.RedirectReason.User_Profile_Url:
                    result = "&rr=up";
                    break;
                case UrlAction.RedirectReason.Custom_Redirect:
                    result = "&rr=cr";
                    break;
            }
            return result;
        }

        #endregion
    }
    internal class DupKeyCheck
    {
        public string TabKey;
        public string TabIdOriginal;
        public string TabPath;
        public bool IsDeleted;
        public DupKeyCheck(string _tabKey, string _tabIdOriginal, string _tabPath, bool _isDeleted)
        {
            TabKey = _tabKey;
            TabIdOriginal = _tabIdOriginal;
            TabPath = _tabPath;
            IsDeleted = _isDeleted;
        }

    }

    [Serializable()]
    public class PathSizes
    {
        public int MaxAliasDepth;
        public int MinAliasDepth;
        public int MaxTabPathDepth;
        public int MinTabPathDepth;
        public void SetAliasDepth(string httpAlias)
        {
            int aliasPathDepth = httpAlias.Length - httpAlias.Replace("/", "").Length;
            if (aliasPathDepth > MaxAliasDepth )
                MaxAliasDepth  = aliasPathDepth;
            if (aliasPathDepth < MinAliasDepth)
                MinAliasDepth  = aliasPathDepth;
        }
        public void SetTabPathDepth(int tabPathDepth)
        {
            if (tabPathDepth > MaxTabPathDepth)
                MaxTabPathDepth = tabPathDepth;
            if (tabPathDepth < MinTabPathDepth)
                MinTabPathDepth = tabPathDepth;
        }
    }
    [Serializable()]
    public class PageIndexData
    {
        public string LastPageKey;
        public string LastPageValue;
    }
}

